﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;

namespace Infrastructure.CrossCutting.Configuration
{
    public class ConfigSection : OrderedDictionary, IConfigSection
    {
        #region ctor

        public ConfigSection()
        {
        }

        public ConfigSection(string name)
        {
            Name = name;
        }

        #endregion

        #region Implementation of IConfigSection

        /// <summary>
        /// Add section key/value item. 
        /// </summary>
        /// <param name="sectionName">Name of the section.</param>
        /// <param name="key">The key.</param>
        /// <param name="val">The val.</param>
        public void AddToSection(string sectionName, string key, object val)
        {
            AddToSection(sectionName, key, val, false);
        }

        /// <summary>
        /// Add to value to the section/key combination. 
        /// </summary>
        /// <param name="sectionName">Name of the section.</param>
        /// <param name="key">The key.</param>
        /// <param name="val">The val.</param>
        /// <param name="overWrite">if set to <c>true</c> [over write].</param>
        public void AddToSection(string sectionName, string key, object val, bool overWrite)
        {
            // First we should get the config section
            object result = this[sectionName];

            IConfigSection section = null;

            if (result == null)
            {
                section = new ConfigSection(sectionName);
                Add(sectionName, section);
            }
            else if (result is IConfigSection)
            {
                section = (IConfigSection)result;
            }
            else if (result is List<object>)
            {
                section = (IConfigSection)((List<object>)result)[0];
            }
            AddToSection(section, key, val, overWrite);
        }

        private static void AddToSection(IDictionary section, string key, object val, bool overWrite)
        {

            object obj = section[key];
            if (obj == null || overWrite)
                section[key] = val;
            else
            {
                List<object> valueList;
                if (obj is List<object>)
                {
                    valueList = (List<object>)obj;
                    valueList.Add(val);
                }
                else
                {
                    valueList = new List<object> { obj, val };
                    section[key] = valueList;
                }
            }
        }

        /// <summary>
        /// Add to value to the section/key combination. 
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="val">The val.</param>
        /// <param name="overWrite">if set to <c>true</c> [over write].</param>
        public void AddItem(string key, object val, bool overWrite)
        {
            AddToSection(this, key, val, overWrite);
        }

        /// <summary>
        /// Add to value to the section/key combination. 
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="val">The val.</param>
        public void AddItem(string key, object val)
        {
            AddToSection(this, key, val, false);
        }

        /// <summary>
        /// Determines whether [contains] [the specified section].
        /// </summary>
        /// <param name="sectionName"></param>
        /// <param name="key">The key.</param>
        /// <returns>
        /// 	<c>true</c> if [contains] [the specified section]; otherwise, <c>false</c>.
        /// </returns>
        public bool Contains(string sectionName, string key)
        {
            var section = this[sectionName] as IConfigSection;
            return section != null && section.Contains(key);
        }

        /// <summary>
        /// Gets the specified key.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">The key.</param>
        /// <exception cref="ConfigurationException"></exception>
        /// <returns></returns>
        public T Get<T>(string key)
        {
            object result = this[key];
            if (result == null)
                throw new ConfigurationException(string.Format("Missing configuration item {0}.", key));
            var converted = (T)Convert<T>(result);
            return converted;
        }

        /// <summary>
        /// Gets the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="result"></param>
        /// <returns></returns>
        public bool TryGet(string key, out object result)
        {
            if (!Contains(key))
            {
                result = null;
                return false;
            }

            result = this[key];
            return true;
        }



        public bool TryGet<T>(string key, out T result)
        {
            result = Get<T>(key);
            return result != null;
        }


        /// <summary>
        /// Gets the specified section.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public T Get<T>(string section, string key)
        {
            object result = this[section, key];
            if (result == null)
                throw new ConfigurationException(string.Format("Missing configuration item {0}.", key));
            var converted = (T)Convert<T>(result);
            return converted;
        }

        public bool TryGet<T>(string section, string key, out T result)
        {
            result = Get<T>(section, key);
            return result != null;
        }

        /// <summary>
        /// Gets the specified section.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public object Get(string section, string key)
        {
            return this[section, key];
        }


        public bool TryGet(string section, string key, out object result)
        {
            result = Get(section, key);
            return result != null;
        }

        /// <summary>
        /// Get key value if preset, default value otherwise. 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public T GetDefault<T>(string key, T defaultValue)
        {
            if (!Contains(key)) return defaultValue;
            return Get<T>(key);
        }

        /// <summary>
        /// Get key value if preset, default value otherwise. 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public T GetDefault<T>(string section, string key, T defaultValue)
        {
            if (!Contains(section, key)) return defaultValue;
            return Get<T>(section, key);
        }

        /// <summary>
        /// Gets the section with the name specified.
        /// </summary>
        /// <param name="sectionName">Name of the section.</param>
        /// <returns></returns>
        public IConfigSection GetSection(string sectionName)
        {
            return this[sectionName] as IConfigSection;
        }

        /// <summary>
        /// Get the section key value using the indexer. 
        /// </summary>
        /// <value></value>
        public object this[string sectionName, string key]
        {
            get
            {
                object section = this[sectionName];
                if (section is IDictionary)
                    return ((IDictionary)section)[key];
                return null;
            }
            set
            {
                object section = this[sectionName];
                if (section is IDictionary)
                    AddToSection((IDictionary)section, key, value, false);
                else
                    AddToSection(sectionName, key, value, false);
            }
        }

        /// <summary>
        /// Gets or sets the name of this config section.
        /// </summary>
        /// <value>The name.</value>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the names of the sections.
        /// </summary>
        /// <value>The sections.</value>
        public List<string> Sections
        {
            get
            {
                var sections = new List<string>();
                foreach (DictionaryEntry entry in this)
                {
                    if (entry.Value is IConfigSection)
                        sections.Add(entry.Key.ToString());
                    else if (entry.Value is List<object>)
                    {
                        var items = (List<object>)entry.Value;
                        if (items[0] is IConfigSection)
                            sections.Add(entry.Key.ToString());
                    }
                }
                return sections;
            }
        }

        #endregion

        /// <summary>
        /// Convert to correct type.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="input"></param>
        /// <returns></returns>
        private static object Convert<T>(object input)
        {
            //if (input == null)
            //    return null;

            if (typeof(T) == typeof(int) || typeof(T) == typeof(int?))
                return System.Convert.ToInt32(input);
            if (typeof(T) == typeof(long) || typeof(T) == typeof(long?))
                return System.Convert.ToInt64(input);
            if (typeof(T) == typeof(string))
                return System.Convert.ToString(input);
            if (typeof(T) == typeof(bool?) || typeof(T) == typeof(bool))
                return System.Convert.ToBoolean(input);
            if (typeof(T) == typeof(double) || typeof(T) == typeof(double?))
                return System.Convert.ToDouble(input);
            if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?))
                return System.Convert.ToDateTime(input);

            return default(T);
        }
    }

}
